require 'net/telnet'
require 'date'
load 'airport.rb'
load 'booking.rb'
load 'pricing.rb'
load 'connection.rb'
load 'simple_time.rb'
load 'multi_hop_flight.rb'
load 'multi_hop_connection.rb'
load 'exceptions.rb'

class Executor
  
  def self.run(command)
    host = Net::Telnet.new('Host' => 'localhost', 'Port' => 12111)
    out =''
    host.cmd(command) do |line|
      if line != nil 
        raise AdameusException, "Wrong input format [" + command + "]" if line.start_with? "ERRIM"
        raise AdameusException, "Empty Command" if line.start_with? "ERREC"
        raise AdameusException, "No results for command " + command if line.start_with? "ERRNR"
        raise AdameusException, "Something pretty bad happened [" + command + "]" if line.start_with? "ERRXX" 
        out += line + "\n"
      else
        out += "None"
      end
    end
    host.close
    return out
  end
  
  def self.version
    return self.run 'V'
  end
  
  def self.airlines 
    result = run 'A'
    lines = result.split("\n")
    airlines_map = Hash.new
    lines.each do |line|
      airlines_map[line[0, 3]] = line[3, line.length-3]
    end
    return airlines_map
  end
  
  def self.airports
    result = run('P')
    airports_map = Hash.new
    result.each_line do |line|
      if line.length > 1 
        key = line[0, 3]
        airo_str = line[3, line.length-3]
        airo_parts = airo_str.split
        airo_str = ""
        airo_parts[1, airo_parts.length - 1].each do |arg|
          airo_str << arg
          airo_str << " "
        end
        airport = Airport.new(key, airo_parts[0], airo_str)
        airports_map[key] = airport
      end
    end
    return airports_map
  end
  
  def self.destinations(airport)
    result = run('D'+airport)
    lines = result.split("\n")
    airports_map = airports
    destinations = Array.new
    lines.each do |line|
      if airports_map.has_key? line
        destinations << airports_map[line]
      end
    end
    raise MarsException, "No destinations from " + airport if destinations.size < 1
    return destinations
  end
  
  def self.connections(from, to , on)
    result = run('C'+from+to+on)   
    result = result == nil ? "" : result
    connections = Array.new
      lines = result.split("\n")      
      lines.each do |line|
        if line.size > 15
          flightnr = line[0, 6]
          departure_parts = line[6, 5].split(":")
          duration_parts = line[11, 5].split(":")
          departure = SimpleTime.new(departure_parts[0], departure_parts[1])
          duration = SimpleTime.new(duration_parts[0], duration_parts[1])
          connection = Connection.new(flightnr, departure, duration,on)
          connections << connection
        end
    end
    raise MarsException, "There are no connections between " + from + " and " + to + " on " + on if connections.size < 1
    return connections
  end
  
  def self.flight(flightnr)
    result = run('F'+flightnr)
    from = result[0,3]
    to = result[3,3]
    airports_map = airports
    arr = Array.new
    if airports_map.has_key? from and airports_map.has_key? to
      from = airports_map[from]
      to = airports_map[to] 
      arr << from
      arr << to
    end
    raise MarsException, "There is no flight with flightnr " + flightnr if arr.size < 2
    return arr
  end
  
  def self.days_of_the_week(flightnr)
    result = run('W'+flightnr)
    days_map = Hash.new
    day = 0
    result.each_char do |char|  
      if char == "Y" || char == "N"
        val = false
        if char == "Y"
          val = true
        end
        days_map[int_to_day(day)] = val
        day += 1
      end
    end
    raise MarsException, "There is no flight with flightnr " + flightnr if days_map.size != 7
    return days_map
  end
  
  def self.pricing(on, flightnr, cat)
    result = run('S'+on+flightnr+cat)
    price_str = result[0, result.length-2]
    details = Pricing.new(price_str[0,3], price_str[3,price_str.length])
    return details
  end
  
  def self.hold(date, flightnr, cat, gender, first_name, surname)
    first_name = first_name[0, 15]
    surname = surname[0, 20]
    result = run('H'+date+flightnr+cat+gender+first_name+' ' *(15 - first_name.length)+surname+' '*(20-surname.length))
    if(result.start_with? "S")
      return result[1,result.length - 1]
    else
      raise MarsException, "Failed to hold seat: no seat available in category " + cat if result[1,1] == "N"
      raise MarsException, "Failed to hold seat: unknown error" 
    end
  end
  
  def self.book(booking_code)
    result = run('B'+booking_code)
    if result.start_with?('S')
      date = result[1, 10]
      departure = result[11, 5]
      departure = SimpleTime.new(departure[0,2], departure[3,2])
      duration = result[16, 5]
      duration = SimpleTime.new(duration[0,2], duration[3,2])
      flightnr = result[21, 6]
      category = result[27, 1]
      gender = result[28, 1]
      first_name = result[29, 15]
      surname = result[44, 20]
      price = result[64, 5]
      booking = Booking.new(booking_code, date, departure, duration, flightnr, category, gender, first_name, surname, price)
      return booking
    else
      raise MarsException, "Booking failed: wrong booking code" if result[1,1] == "N"
      raise MarsException, "Booking failed: seat already booked" if result[1,1] == "A"
      raise MarsException, "Booking failed: unknown error"
    end
  end 
  
  def self.cancel(booking_code)
    result = run('X'+booking_code)
    if result.start_with? "S"
      return "Cancelation succesfull"
    else
      raise MarsException, "Cancelation failed: no such booking" if result[1,1] == "N"
      raise MarsException, "Cancelation failed: booking is older than 7 days" if result[1] == "X"
      raise MarsException, "Cancelation failed: unknown error" 
    end    
  end
  
  def self.query_booking(bookingnr)
    result = run('Q'+bookingnr)
    if result.start_with?('F')
      raise MarsException, 'Query failed: no such booking' if result[1,1] == 'N'
      raise MarsException, 'Query failed: unknown error'
    else
      date = result[1, 10]
      departure = result[11, 5]
      departure = SimpleTime.new(departure[0,2], departure[3,2])
      duration = result[16, 5]
      duration = SimpleTime.new(duration[0,2], duration[3,2])
      flightnr = result[21, 6]
      category = result[27, 1]
      gender = result[28, 1]
      first_name = result[29, 15]
      surname = result[44, 20]
      price = result[64, 5]
      booking = Booking.new(bookingnr,date, departure, duration, flightnr, category, gender, first_name, surname, price)
      return booking
    end
  end
#  def self.remove_leading_zeros(number)
#    result = ''
#    number.each_char do |char|
#      if char != '0'
#        result << char
#      end
#    end
#    return result
#  end
  
  def self.int_to_day(int)
    case int
    when 0
      return "monday"
    when 1
      return "tuesday"
    when 2
      return "wednesday"
    when 3
      return "thursday"
    when 4
      return "friday"
    when 5
      return "saturday"
    when 6
      return "sunday"
    else
      return "unknown_day"
    end
  end

  # get all possible multi hop flights from airport to airport with a maximum given hops (default=3)
  # @Example BRU->AMS => [BRU->AMS,BRU->TEG->AMS,BRU->VIE->AMS,BRU->CDG->AMS]
  def self.multi_hop_flights(from,to,max_hops=3)
    # create a resultList
    result_list = Array.new
    # create flights array
    flights_list = Array.new
    # get map with all airports
    airports_map = airports
    raise MarsException, "Unknown airport from [" + from + "]" if(!airports_map.has_key?(from))
    raise MarsException, "Unknown airport to [" + to + "]" if(!airports_map.has_key?(to))
    from = airports_map[from]
    to = airports_map[to]
    dest_from = Hash.new
    # start with current flight that starts with given from airportcode
    current_flight = MultiHopFlight.new([from])
    while(!current_flight.nil?)
      if(current_flight.destination.equal?(to))
        result_list << current_flight
      elsif(current_flight.hop_count < max_hops)
        # Get all destinations from the current_flight's current destionation
        if(!dest_from.has_key?(current_flight.destination.code))
          dest_from[current_flight.destination.code] = Executor.destinations(current_flight.destination.code)
        end
        # add for each destination a new multi_flight to the queue
        dest_from[current_flight.destination.code].each do |dest|
          if(!dest.nil? && !current_flight.visits?(airports_map[dest.code.to_s]))
            new_flight = MultiHopFlight.new(current_flight.hops)
            new_flight.add_hop(airports_map[dest.code])
            flights_list << new_flight
          end
        end
      end
      current_flight = flights_list.shift
    end
    return result_list
  end

  # @return multiHopConnection for the given multi flight, starting on a specified date
  # So the connections between the different hops is searched for, so that they are in the right sequence
  def self.multi_flight_to_connection(multi_flight,start_date,max_days=7)
    raise MarsException, "Flight must be between at least two hops" if(multi_flight.hop_count < 2)
    current_date = Date.parse(start_date)
    m_connection = MultiHopConnection.new(multi_flight)
    hops = multi_flight.hops
    from = hops.shift
    last_arrival = nil
    # Time that is needed to make it to the next flight, Hardcoded 1 hour
    bording_time = SimpleTime.new(0,59)
    while(hops.length > 0)
      to = hops.first
      # find first connection starting from date
      connection_found = false
      days = 0
      connection_to_add = nil
      # maximum waiting time between 2 flights
      while(!connection_found)
        pos_cons = Array.new
        begin
          pos_cons = Executor.connections(from.code, to.code, current_date.to_s)
        rescue MarsException => e
            
        end
        while(!pos_cons.empty?)
          new_con = pos_cons.shift
          # if( (searching for first_connection OR arrival of last connection is before departure of the new)
          # => AND (First connection found to add OR connection that is found arrives before the previously found connections))
          if((last_arrival === nil || last_arrival.before?(new_con.departure)) &&
                (connection_to_add === nil || new_con.arrival.before?(connection_to_add.arrival)))
              connection_found = true
              connection_to_add = new_con
          end
        end
        if(connection_found)
          last_arrival = connection_to_add.arrival
          #add the bording time to the last arrival time
          last_arrival.add(bording_time)
          m_connection.add_connection(connection_to_add)
          current_date += connection_to_add.day_count
        elsif(days > max_days)
          # TODO ERROR no connection in a max amount of days
          raise MarsException, "No connections found in " + max_days.to_s + " days"
        else
          last_arrival = (last_arrival != nil && last_arrival.hour > 23 ? SimpleTime.new(last_arrival.hour-24,last_arrival.minute) : nil)
          current_date += 1
          days += 1
        end
      end
      from = hops.shift
    end
    return m_connection
  end

  # @return All possible connections between 2 airports via max hops(default=3)
  # => List of MultiHopConnection
  def self.multi_flight_connections(from,to,ondate,max_hops=3)
    flights = multi_hop_flights(from, to, max_hops)
    connections = []
    flights.each do |flight|
      m_connection = multi_flight_to_connection(flight, ondate)
      if(m_connection != nil)
        connections << m_connection
      end
    end
    return connections
  end
  
  #Book codes from a list of booking codes if possible, if one can't be booked, cancel all and raise exception
  def self.book_codes(booking_codes)
    bookings = Array.new
    booking_codes.each do |code|
      begin
        result = Executor.book(code)      
        bookings << result
      rescue 
        for bcode in booking_codes
          Executor.cancel(bcode)
        end
        raise
      end
    end
    return bookings
  end
  
end
